<!--
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->

<link rel="import" href="../../../shadycss/apply-shim.html">
<link rel="import" href="../mixins/element-mixin.html">
<link rel="import" href="../mixins/gesture-event-listeners.html">
<link rel="import" href="../mixins/dir-mixin.html">
<link rel="import" href="../utils/mixin.html">
<link rel="import" href="../utils/import-href.html">
<link rel="import" href="../utils/render-status.html">
<link rel="import" href="../utils/unresolved.html">
<link rel="import" href="polymer.dom.html">

<script>
(function() {

  'use strict';

  let styleInterface = window.ShadyCSS;

  /**
   * Element class mixin that provides Polymer's "legacy" API intended to be
   * backward-compatible to the greatest extent possible with the API
   * found on the Polymer 1.x `Polymer.Base` prototype applied to all elements
   * defined using the `Polymer({...})` function.
   *
   * @mixinFunction
   * @polymer
   * @appliesMixin Polymer.ElementMixin
   * @appliesMixin Polymer.GestureEventListeners
   * @property isAttached {boolean} Set to `true` in this element's
   *   `connectedCallback` and `false` in `disconnectedCallback`
   * @memberof Polymer
   * @summary Element class mixin that provides Polymer's "legacy" API
   */
  Polymer.LegacyElementMixin = Polymer.dedupingMixin((base) => {

    /**
     * @constructor
     * @extends {base}
     * @implements {Polymer_ElementMixin}
     * @implements {Polymer_GestureEventListeners}
     * @implements {Polymer_DirMixin}
     */
    const legacyElementBase = Polymer.DirMixin(Polymer.GestureEventListeners(Polymer.ElementMixin(base)));

    /**
     * Map of simple names to touch action names
     * @dict
     */
    const DIRECTION_MAP = {
      'x': 'pan-x',
      'y': 'pan-y',
      'none': 'none',
      'all': 'auto'
    };

    /**
     * @polymer
     * @mixinClass
     * @extends {legacyElementBase}
     * @implements {Polymer_LegacyElementMixin}
     * @unrestricted
     */
    class LegacyElement extends legacyElementBase {

      constructor() {
        super();
        this.root = this;
        /** @type {boolean} */
        this.isAttached;
        /** @type {WeakMap<!Element, !Object<string, !Function>>} */
        this.__boundListeners;
        /** @type {Object<string, Function>} */
        this._debouncers;
        this.created();
        // Ensure listeners are applied immediately so that they are
        // added before declarative event listeners. This allows an element to
        // decorate itself via an event prior to any declarative listeners
        // seeing the event. Note, this ensures compatibility with 1.x ordering.
        this._applyListeners();
      }

      /**
       * Legacy callback called during the `constructor`, for overriding
       * by the user.
       * @return {void}
       */
      created() {}

      /**
       * Provides an implementation of `connectedCallback`
       * which adds Polymer legacy API's `attached` method.
       * @return {void}
       * @override
       */
      connectedCallback() {
        super.connectedCallback();
        this.isAttached = true;
        this.attached();
      }

      /**
       * Legacy callback called during `connectedCallback`, for overriding
       * by the user.
       * @return {void}
       */
      attached() {}

      /**
       * Provides an implementation of `disconnectedCallback`
       * which adds Polymer legacy API's `detached` method.
       * @return {void}
       * @override
       */
      disconnectedCallback() {
        super.disconnectedCallback();
        this.isAttached = false;
        this.detached();
      }

      /**
       * Legacy callback called during `disconnectedCallback`, for overriding
       * by the user.
       * @return {void}
       */
      detached() {}

      /**
       * Provides an override implementation of `attributeChangedCallback`
       * which adds the Polymer legacy API's `attributeChanged` method.
       * @param {string} name Name of attribute.
       * @param {?string} old Old value of attribute.
       * @param {?string} value Current value of attribute.
       * @return {void}
       * @override
       */
      attributeChangedCallback(name, old, value) {
        if (old !== value) {
          super.attributeChangedCallback(name, old, value);
          this.attributeChanged(name, old, value);
        }
      }

      /**
       * Legacy callback called during `attributeChangedChallback`, for overriding
       * by the user.
       * @param {string} name Name of attribute.
       * @param {?string} old Old value of attribute.
       * @param {?string} value Current value of attribute.
       * @return {void}
       */
      attributeChanged(name, old, value) {} // eslint-disable-line no-unused-vars

      /**
       * Overrides the default `Polymer.PropertyEffects` implementation to
       * add support for class initialization via the `_registered` callback.
       * This is called only when the first instance of the element is created.
       *
       * @return {void}
       * @override
       */
      _initializeProperties() {
        let proto = Object.getPrototypeOf(this);
        if (!proto.hasOwnProperty('__hasRegisterFinished')) {
          proto.__hasRegisterFinished = true;
          this._registered();
        }
        super._initializeProperties();
      }

      /**
       * Called automatically when an element is initializing.
       * Users may override this method to perform class registration time
       * work. The implementation should ensure the work is performed
       * only once for the class.
       * @protected
       * @return {void}
       */
      _registered() {}

      /**
       * Overrides the default `Polymer.PropertyEffects` implementation to
       * add support for installing `hostAttributes` and `listeners`.
       *
       * @return {void}
       * @override
       */
      ready() {
        this._ensureAttributes();
        super.ready();
      }

      /**
       * Ensures an element has required attributes. Called when the element
       * is being readied via `ready`. Users should override to set the
       * element's required attributes. The implementation should be sure
       * to check and not override existing attributes added by
       * the user of the element. Typically, setting attributes should be left
       * to the element user and not done here; reasonable exceptions include
       * setting aria roles and focusability.
       * @protected
       * @return {void}
       */
      _ensureAttributes() {}

      /**
       * Adds element event listeners. Called when the element
       * is being readied via `ready`. Users should override to
       * add any required element event listeners.
       * In performance critical elements, the work done here should be kept
       * to a minimum since it is done before the element is rendered. In
       * these elements, consider adding listeners asynchronously so as not to
       * block render.
       * @protected
       * @return {void}
       */
      _applyListeners() {}

      /**
       * Converts a typed JavaScript value to a string.
       *
       * Note this method is provided as backward-compatible legacy API
       * only.  It is not directly called by any Polymer features. To customize
       * how properties are serialized to attributes for attribute bindings and
       * `reflectToAttribute: true` properties as well as this method, override
       * the `_serializeValue` method provided by `Polymer.PropertyAccessors`.
       *
       * @param {*} value Value to deserialize
       * @return {string | undefined} Serialized value
       */
      serialize(value) {
        return this._serializeValue(value);
      }

      /**
       * Converts a string to a typed JavaScript value.
       *
       * Note this method is provided as backward-compatible legacy API
       * only.  It is not directly called by any Polymer features.  To customize
       * how attributes are deserialized to properties for in
       * `attributeChangedCallback`, override `_deserializeValue` method
       * provided by `Polymer.PropertyAccessors`.
       *
       * @param {string} value String to deserialize
       * @param {*} type Type to deserialize the string to
       * @return {*} Returns the deserialized value in the `type` given.
       */
      deserialize(value, type) {
        return this._deserializeValue(value, type);
      }

      /**
       * Serializes a property to its associated attribute.
       *
       * Note this method is provided as backward-compatible legacy API
       * only.  It is not directly called by any Polymer features.
       *
       * @param {string} property Property name to reflect.
       * @param {string=} attribute Attribute name to reflect.
       * @param {*=} value Property value to reflect.
       * @return {void}
       */
      reflectPropertyToAttribute(property, attribute, value) {
        this._propertyToAttribute(property, attribute, value);
      }

      /**
       * Sets a typed value to an HTML attribute on a node.
       *
       * Note this method is provided as backward-compatible legacy API
       * only.  It is not directly called by any Polymer features.
       *
       * @param {*} value Value to serialize.
       * @param {string} attribute Attribute name to serialize to.
       * @param {Element} node Element to set attribute to.
       * @return {void}
       */
      serializeValueToAttribute(value, attribute, node) {
        this._valueToNodeAttribute(/** @type {Element} */ (node || this), value, attribute);
      }

      /**
       * Copies own properties (including accessor descriptors) from a source
       * object to a target object.
       *
       * @param {Object} prototype Target object to copy properties to.
       * @param {Object} api Source object to copy properties from.
       * @return {Object} prototype object that was passed as first argument.
       */
      extend(prototype, api) {
        if (!(prototype && api)) {
          return prototype || api;
        }
        let n$ = Object.getOwnPropertyNames(api);
        for (let i=0, n; (i<n$.length) && (n=n$[i]); i++) {
          let pd = Object.getOwnPropertyDescriptor(api, n);
          if (pd) {
            Object.defineProperty(prototype, n, pd);
          }
        }
        return prototype;
      }

      /**
       * Copies props from a source object to a target object.
       *
       * Note, this method uses a simple `for...in` strategy for enumerating
       * properties.  To ensure only `ownProperties` are copied from source
       * to target and that accessor implementations are copied, use `extend`.
       *
       * @param {Object} target Target object to copy properties to.
       * @param {Object} source Source object to copy properties from.
       * @return {Object} Target object that was passed as first argument.
       */
      mixin(target, source) {
        for (let i in source) {
          target[i] = source[i];
        }
        return target;
      }

      /**
       * Sets the prototype of an object.
       *
       * Note this method is provided as backward-compatible legacy API
       * only.  It is not directly called by any Polymer features.
       * @param {Object} object The object on which to set the prototype.
       * @param {Object} prototype The prototype that will be set on the given
       * `object`.
       * @return {Object} Returns the given `object` with its prototype set
       * to the given `prototype` object.
       */
      chainObject(object, prototype) {
        if (object && prototype && object !== prototype) {
          object.__proto__ = prototype;
        }
        return object;
      }

      /* **** Begin Template **** */

      /**
       * Calls `importNode` on the `content` of the `template` specified and
       * returns a document fragment containing the imported content.
       *
       * @param {HTMLTemplateElement} template HTML template element to instance.
       * @return {DocumentFragment} Document fragment containing the imported
       *   template content.
      */
      instanceTemplate(template) {
        let content = this.constructor._contentForTemplate(template);
        let dom = /** @type {DocumentFragment} */
          (document.importNode(content, true));
        return dom;
      }

      /* **** Begin Events **** */



      /**
       * Dispatches a custom event with an optional detail value.
       *
       * @param {string} type Name of event type.
       * @param {*=} detail Detail value containing event-specific
       *   payload.
       * @param {{ bubbles: (boolean|undefined), cancelable: (boolean|undefined), composed: (boolean|undefined) }=}
       *  options Object specifying options.  These may include:
       *  `bubbles` (boolean, defaults to `true`),
       *  `cancelable` (boolean, defaults to false), and
       *  `node` on which to fire the event (HTMLElement, defaults to `this`).
       * @return {Event} The new event that was fired.
       */
      fire(type, detail, options) {
        options = options || {};
        detail = (detail === null || detail === undefined) ? {} : detail;
        let event = new Event(type, {
          bubbles: options.bubbles === undefined ? true : options.bubbles,
          cancelable: Boolean(options.cancelable),
          composed: options.composed === undefined ? true: options.composed
        });
        event.detail = detail;
        let node = options.node || this;
        node.dispatchEvent(event);
        return event;
      }

      /**
       * Convenience method to add an event listener on a given element,
       * late bound to a named method on this element.
       *
       * @param {Element} node Element to add event listener to.
       * @param {string} eventName Name of event to listen for.
       * @param {string} methodName Name of handler method on `this` to call.
       * @return {void}
       */
      listen(node, eventName, methodName) {
        node = /** @type {!Element} */ (node || this);
        let hbl = this.__boundListeners ||
          (this.__boundListeners = new WeakMap());
        let bl = hbl.get(node);
        if (!bl) {
          bl = {};
          hbl.set(node, bl);
        }
        let key = eventName + methodName;
        if (!bl[key]) {
          bl[key] = this._addMethodEventListenerToNode(
            node, eventName, methodName, this);
        }
      }

      /**
       * Convenience method to remove an event listener from a given element,
       * late bound to a named method on this element.
       *
       * @param {Element} node Element to remove event listener from.
       * @param {string} eventName Name of event to stop listening to.
       * @param {string} methodName Name of handler method on `this` to not call
       anymore.
       * @return {void}
       */
      unlisten(node, eventName, methodName) {
        node = /** @type {!Element} */ (node || this);
        let bl = this.__boundListeners && this.__boundListeners.get(node);
        let key = eventName + methodName;
        let handler = bl && bl[key];
        if (handler) {
          this._removeEventListenerFromNode(node, eventName, handler);
          bl[key] = null;
        }
      }

      /**
       * Override scrolling behavior to all direction, one direction, or none.
       *
       * Valid scroll directions:
       *   - 'all': scroll in any direction
       *   - 'x': scroll only in the 'x' direction
       *   - 'y': scroll only in the 'y' direction
       *   - 'none': disable scrolling for this node
       *
       * @param {string=} direction Direction to allow scrolling
       * Defaults to `all`.
       * @param {Element=} node Element to apply scroll direction setting.
       * Defaults to `this`.
       * @return {void}
       */
      setScrollDirection(direction, node) {
        Polymer.Gestures.setTouchAction(/** @type {Element} */ (node || this), DIRECTION_MAP[direction] || 'auto');
      }
      /* **** End Events **** */

      /**
       * Convenience method to run `querySelector` on this local DOM scope.
       *
       * This function calls `Polymer.dom(this.root).querySelector(slctr)`.
       *
       * @param {string} slctr Selector to run on this local DOM scope
       * @return {Element} Element found by the selector, or null if not found.
       */
      $$(slctr) {
        return this.root.querySelector(slctr);
      }

      /**
       * Return the element whose local dom within which this element
       * is contained. This is a shorthand for
       * `this.getRootNode().host`.
       * @this {Element}
       */
      get domHost() {
        let root = this.getRootNode();
        return (root instanceof DocumentFragment) ? /** @type {ShadowRoot} */ (root).host : root;
      }

      /**
       * Force this element to distribute its children to its local dom.
       * This should not be necessary as of Polymer 2.0.2 and is provided only
       * for backwards compatibility.
       * @return {void}
       */
      distributeContent() {
        if (window.ShadyDOM && this.shadowRoot) {
          ShadyDOM.flush();
        }
      }

      /**
       * Returns a list of nodes that are the effective childNodes. The effective
       * childNodes list is the same as the element's childNodes except that
       * any `<content>` elements are replaced with the list of nodes distributed
       * to the `<content>`, the result of its `getDistributedNodes` method.
       * @return {!Array<!Node>} List of effective child nodes.
       * @suppress {invalidCasts} LegacyElementMixin must be applied to an HTMLElement
       */
      getEffectiveChildNodes() {
        const thisEl = /** @type {Element} */ (this);
        const domApi = /** @type {Polymer.DomApi} */(Polymer.dom(thisEl));
        return domApi.getEffectiveChildNodes();
      }

      /**
       * Returns a list of nodes distributed within this element that match
       * `selector`. These can be dom children or elements distributed to
       * children that are insertion points.
       * @param {string} selector Selector to run.
       * @return {!Array<!Node>} List of distributed elements that match selector.
       * @suppress {invalidCasts} LegacyElementMixin must be applied to an HTMLElement
       */
      queryDistributedElements(selector) {
        const thisEl = /** @type {Element} */ (this);
        const domApi = /** @type {Polymer.DomApi} */(Polymer.dom(thisEl));
        return domApi.queryDistributedElements(selector);
      }

      /**
       * Returns a list of elements that are the effective children. The effective
       * children list is the same as the element's children except that
       * any `<content>` elements are replaced with the list of elements
       * distributed to the `<content>`.
       *
       * @return {!Array<!Node>} List of effective children.
       */
      getEffectiveChildren() {
        let list = this.getEffectiveChildNodes();
        return list.filter(function(/** @type {!Node} */ n) {
          return (n.nodeType === Node.ELEMENT_NODE);
        });
      }

      /**
       * Returns a string of text content that is the concatenation of the
       * text content's of the element's effective childNodes (the elements
       * returned by <a href="#getEffectiveChildNodes>getEffectiveChildNodes</a>.
       *
       * @return {string} List of effective children.
       */
      getEffectiveTextContent() {
        let cn = this.getEffectiveChildNodes();
        let tc = [];
        for (let i=0, c; (c = cn[i]); i++) {
          if (c.nodeType !== Node.COMMENT_NODE) {
            tc.push(c.textContent);
          }
        }
        return tc.join('');
      }

      /**
       * Returns the first effective childNode within this element that
       * match `selector`. These can be dom child nodes or elements distributed
       * to children that are insertion points.
       * @param {string} selector Selector to run.
       * @return {Node} First effective child node that matches selector.
       */
      queryEffectiveChildren(selector) {
        let e$ = this.queryDistributedElements(selector);
        return e$ && e$[0];
      }

      /**
       * Returns a list of effective childNodes within this element that
       * match `selector`. These can be dom child nodes or elements distributed
       * to children that are insertion points.
       * @param {string} selector Selector to run.
       * @return {!Array<!Node>} List of effective child nodes that match selector.
       */
      queryAllEffectiveChildren(selector) {
        return this.queryDistributedElements(selector);
      }

      /**
       * Returns a list of nodes distributed to this element's `<slot>`.
       *
       * If this element contains more than one `<slot>` in its local DOM,
       * an optional selector may be passed to choose the desired content.
       *
       * @param {string=} slctr CSS selector to choose the desired
       *   `<slot>`.  Defaults to `content`.
       * @return {!Array<!Node>} List of distributed nodes for the `<slot>`.
       */
      getContentChildNodes(slctr) {
        let content = this.root.querySelector(slctr || 'slot');
        return content ? /** @type {Polymer.DomApi} */(Polymer.dom(content)).getDistributedNodes() : [];
      }

      /**
       * Returns a list of element children distributed to this element's
       * `<slot>`.
       *
       * If this element contains more than one `<slot>` in its
       * local DOM, an optional selector may be passed to choose the desired
       * content.  This method differs from `getContentChildNodes` in that only
       * elements are returned.
       *
       * @param {string=} slctr CSS selector to choose the desired
       *   `<content>`.  Defaults to `content`.
       * @return {!Array<!HTMLElement>} List of distributed nodes for the
       *   `<slot>`.
       * @suppress {invalidCasts}
       */
      getContentChildren(slctr) {
        let children = /** @type {!Array<!HTMLElement>} */(this.getContentChildNodes(slctr).filter(function(n) {
          return (n.nodeType === Node.ELEMENT_NODE);
        }));
        return children;
      }

      /**
       * Checks whether an element is in this element's light DOM tree.
       *
       * @param {?Node} node The element to be checked.
       * @return {boolean} true if node is in this element's light DOM tree.
       * @suppress {invalidCasts} LegacyElementMixin must be applied to an HTMLElement
       */
      isLightDescendant(node) {
        const thisNode = /** @type {Node} */ (this);
        return thisNode !== node && thisNode.contains(node) &&
          thisNode.getRootNode() === node.getRootNode();
      }

      /**
       * Checks whether an element is in this element's local DOM tree.
       *
       * @param {Element=} node The element to be checked.
       * @return {boolean} true if node is in this element's local DOM tree.
       */
      isLocalDescendant(node) {
        return this.root === node.getRootNode();
      }

      /**
       * No-op for backwards compatibility. This should now be handled by
       * ShadyCss library.
       * @param  {*} container Unused
       * @param  {*} shouldObserve Unused
       * @return {void}
       */
      scopeSubtree(container, shouldObserve) { // eslint-disable-line no-unused-vars
      }

      /**
       * Returns the computed style value for the given property.
       * @param {string} property The css property name.
       * @return {string} Returns the computed css property value for the given
       * `property`.
       * @suppress {invalidCasts} LegacyElementMixin must be applied to an HTMLElement
       */
      getComputedStyleValue(property) {
        return styleInterface.getComputedStyleValue(/** @type {!Element} */(this), property);
      }

      // debounce

      /**
       * Call `debounce` to collapse multiple requests for a named task into
       * one invocation which is made after the wait time has elapsed with
       * no new request.  If no wait time is given, the callback will be called
       * at microtask timing (guaranteed before paint).
       *
       *     debouncedClickAction(e) {
       *       // will not call `processClick` more than once per 100ms
       *       this.debounce('click', function() {
       *        this.processClick();
       *       } 100);
       *     }
       *
       * @param {string} jobName String to identify the debounce job.
       * @param {function():void} callback Function that is called (with `this`
       *   context) when the wait time elapses.
       * @param {number} wait Optional wait time in milliseconds (ms) after the
       *   last signal that must elapse before invoking `callback`
       * @return {Object} Returns a debouncer object on which exists the
       * following methods: `isActive()` returns true if the debouncer is
       * active; `cancel()` cancels the debouncer if it is active;
       * `flush()` immediately invokes the debounced callback if the debouncer
       * is active.
       */
      debounce(jobName, callback, wait) {
        this._debouncers = this._debouncers || {};
        return this._debouncers[jobName] = Polymer.Debouncer.debounce(
              this._debouncers[jobName]
            , wait > 0 ? Polymer.Async.timeOut.after(wait) : Polymer.Async.microTask
            , callback.bind(this));
      }

      /**
       * Returns whether a named debouncer is active.
       *
       * @param {string} jobName The name of the debouncer started with `debounce`
       * @return {boolean} Whether the debouncer is active (has not yet fired).
       */
      isDebouncerActive(jobName) {
        this._debouncers = this._debouncers || {};
        let debouncer = this._debouncers[jobName];
        return !!(debouncer && debouncer.isActive());
      }

      /**
       * Immediately calls the debouncer `callback` and inactivates it.
       *
       * @param {string} jobName The name of the debouncer started with `debounce`
       * @return {void}
       */
      flushDebouncer(jobName) {
        this._debouncers = this._debouncers || {};
        let debouncer = this._debouncers[jobName];
        if (debouncer) {
          debouncer.flush();
        }
      }

      /**
       * Cancels an active debouncer.  The `callback` will not be called.
       *
       * @param {string} jobName The name of the debouncer started with `debounce`
       * @return {void}
       */
      cancelDebouncer(jobName) {
        this._debouncers = this._debouncers || {};
        let debouncer = this._debouncers[jobName];
        if (debouncer) {
          debouncer.cancel();
        }
      }

      /**
       * Runs a callback function asynchronously.
       *
       * By default (if no waitTime is specified), async callbacks are run at
       * microtask timing, which will occur before paint.
       *
       * @param {!Function} callback The callback function to run, bound to `this`.
       * @param {number=} waitTime Time to wait before calling the
       *   `callback`.  If unspecified or 0, the callback will be run at microtask
       *   timing (before paint).
       * @return {number} Handle that may be used to cancel the async job.
       */
      async(callback, waitTime) {
        return waitTime > 0 ? Polymer.Async.timeOut.run(callback.bind(this), waitTime) :
            ~Polymer.Async.microTask.run(callback.bind(this));
      }

      /**
       * Cancels an async operation started with `async`.
       *
       * @param {number} handle Handle returned from original `async` call to
       *   cancel.
       * @return {void}
       */
      cancelAsync(handle) {
        handle < 0 ? Polymer.Async.microTask.cancel(~handle) :
            Polymer.Async.timeOut.cancel(handle);
      }

      // other

      /**
       * Convenience method for creating an element and configuring it.
       *
       * @param {string} tag HTML element tag to create.
       * @param {Object} props Object of properties to configure on the
       *    instance.
       * @return {Element} Newly created and configured element.
       */
      create(tag, props) {
        let elt = document.createElement(tag);
        if (props) {
          if (elt.setProperties) {
            elt.setProperties(props);
          } else {
            for (let n in props) {
              elt[n] = props[n];
            }
          }
        }
        return elt;
      }

      /**
       * Convenience method for importing an HTML document imperatively.
       *
       * This method creates a new `<link rel="import">` element with
       * the provided URL and appends it to the document to start loading.
       * In the `onload` callback, the `import` property of the `link`
       * element will contain the imported document contents.
       *
       * @param {string} href URL to document to load.
       * @param {!Function=} onload Callback to notify when an import successfully
       *   loaded.
       * @param {!Function=} onerror Callback to notify when an import
       *   unsuccessfully loaded.
       * @param {boolean=} optAsync True if the import should be loaded `async`.
       *   Defaults to `false`.
       * @return {!HTMLLinkElement} The link element for the URL to be loaded.
       */
      importHref(href, onload, onerror, optAsync) { // eslint-disable-line no-unused-vars
        let loadFn = onload ? onload.bind(this) : null;
        let errorFn = onerror ? onerror.bind(this) : null;
        return Polymer.importHref(href, loadFn, errorFn, optAsync);
      }

      /**
       * Polyfill for Element.prototype.matches, which is sometimes still
       * prefixed.
       *
       * @param {string} selector Selector to test.
       * @param {!Element=} node Element to test the selector against.
       * @return {boolean} Whether the element matches the selector.
       */
      elementMatches(selector, node) {
        return Polymer.dom.matchesSelector(/** @type {!Element} */ (node || this), selector);
      }

      /**
       * Toggles an HTML attribute on or off.
       *
       * @param {string} name HTML attribute name
       * @param {boolean=} bool Boolean to force the attribute on or off.
       *    When unspecified, the state of the attribute will be reversed.
       * @param {Element=} node Node to target.  Defaults to `this`.
       * @return {void}
       */
      toggleAttribute(name, bool, node) {
        node = /** @type {Element} */ (node || this);
        if (arguments.length == 1) {
          bool = !node.hasAttribute(name);
        }
        if (bool) {
          node.setAttribute(name, '');
        } else {
          node.removeAttribute(name);
        }
      }


      /**
       * Toggles a CSS class on or off.
       *
       * @param {string} name CSS class name
       * @param {boolean=} bool Boolean to force the class on or off.
       *    When unspecified, the state of the class will be reversed.
       * @param {Element=} node Node to target.  Defaults to `this`.
       * @return {void}
       */
      toggleClass(name, bool, node) {
        node = /** @type {Element} */ (node || this);
        if (arguments.length == 1) {
          bool = !node.classList.contains(name);
        }
        if (bool) {
          node.classList.add(name);
        } else {
          node.classList.remove(name);
        }
      }

      /**
       * Cross-platform helper for setting an element's CSS `transform` property.
       *
       * @param {string} transformText Transform setting.
       * @param {Element=} node Element to apply the transform to.
       * Defaults to `this`
       * @return {void}
       */
      transform(transformText, node) {
        node = /** @type {Element} */ (node || this);
        node.style.webkitTransform = transformText;
        node.style.transform = transformText;
      }

      /**
       * Cross-platform helper for setting an element's CSS `translate3d`
       * property.
       *
       * @param {number} x X offset.
       * @param {number} y Y offset.
       * @param {number} z Z offset.
       * @param {Element=} node Element to apply the transform to.
       * Defaults to `this`.
       * @return {void}
       */
      translate3d(x, y, z, node) {
        node = /** @type {Element} */ (node || this);
        this.transform('translate3d(' + x + ',' + y + ',' + z + ')', node);
      }

      /**
       * Removes an item from an array, if it exists.
       *
       * If the array is specified by path, a change notification is
       * generated, so that observers, data bindings and computed
       * properties watching that path can update.
       *
       * If the array is passed directly, **no change
       * notification is generated**.
       *
       * @param {string | !Array<number|string>} arrayOrPath Path to array from which to remove the item
       *   (or the array itself).
       * @param {*} item Item to remove.
       * @return {Array} Array containing item removed.
       */
      arrayDelete(arrayOrPath, item) {
        let index;
        if (Array.isArray(arrayOrPath)) {
          index = arrayOrPath.indexOf(item);
          if (index >= 0) {
            return arrayOrPath.splice(index, 1);
          }
        } else {
          let arr = Polymer.Path.get(this, arrayOrPath);
          index = arr.indexOf(item);
          if (index >= 0) {
            return this.splice(arrayOrPath, index, 1);
          }
        }
        return null;
      }

      // logging

      /**
       * Facades `console.log`/`warn`/`error` as override point.
       *
       * @param {string} level One of 'log', 'warn', 'error'
       * @param {Array} args Array of strings or objects to log
       * @return {void}
       */
      _logger(level, args) {
        // accept ['foo', 'bar'] and [['foo', 'bar']]
        if (Array.isArray(args) && args.length === 1) {
          args = args[0];
        }
        switch(level) {
          case 'log':
          case 'warn':
          case 'error':
            console[level](...args);
        }
      }

      /**
       * Facades `console.log` as an override point.
       *
       * @param {...*} args Array of strings or objects to log
       * @return {void}
       */
      _log(...args) {
        this._logger('log', args);
      }

      /**
       * Facades `console.warn` as an override point.
       *
       * @param {...*} args Array of strings or objects to log
       * @return {void}
       */
      _warn(...args) {
        this._logger('warn', args);
      }

      /**
       * Facades `console.error` as an override point.
       *
       * @param {...*} args Array of strings or objects to log
       * @return {void}
       */
      _error(...args) {
        this._logger('error', args);
      }

      /**
       * Formats a message using the element type an a method name.
       *
       * @param {string} methodName Method name to associate with message
       * @param {...*} args Array of strings or objects to log
       * @return {Array} Array with formatting information for `console`
       *   logging.
       */
      _logf(methodName, ...args) {
        return ['[%s::%s]', this.is, methodName, ...args];
      }

    }

    LegacyElement.prototype.is = '';

    return LegacyElement;

  });

})();
</script>
